package concordia.comp6231.leaderelection;

import static concordia.comp6231.common.ApplicationConfig.LEADERELECTION_SERVER_PORT;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import concordia.comp6231.common.ApplicationConfig;
import concordia.comp6231.groupmembership.GroupMembershipManager;
import concordia.comp6231.rfmulticast.ListernerHandler;
import concordia.comp6231.rfmulticast.Message;
import concordia.comp6231.rfmulticast.RFIFOMulticast;
import concordia.comp6231.rfmulticast.ResultHandler;
import concordia.comp6231.server.ReplicaMember;


public class LeaderElectionManager {

	private static String LE_TYPE_COORD_MSG = "LE_TYPE_COORD_MSG";
	private static String LE_TYPE_GET_ID    = "LE_TYPE_GET_ID";
	
	private static final String KEY_MSG_TYPE = "MSG_TYPE";
	private static final String REPLICA_PROCESS_ID = "REPLICA_PROCESS_ID";

	private int currentLeaderProcessId;
	private String currentLeaderHostname;
	private RFIFOMulticast rfms;
	private Map<String, Integer> replicaIds = new HashMap<String, Integer>();
	int currentIdService;
	private static LeaderElectionManager leaderElectionManager;

	private LeaderElectionManager() {
	}
	
	public static LeaderElectionManager currentInstance() {
		if (leaderElectionManager == null) {
			leaderElectionManager = new LeaderElectionManager();
		}
		return leaderElectionManager;
	}
	
	public void startService() {
		rfms = new RFIFOMulticast();
		currentIdService = ReplicaMember.currentInstance().getProcessId();
		rfms.listen(ApplicationConfig.getAsInt(LEADERELECTION_SERVER_PORT), listenerHandler);
		electNewLeader();
	}
	
	/*
	 * Using the bully algorithm the new leader is elected
	 */
	public void electNewLeader() {
		replicaIds.clear();
		Message giveMeYourIdMsg = new Message();
		giveMeYourIdMsg.put(KEY_MSG_TYPE, LE_TYPE_GET_ID);
		int currentGroupSize = GroupMembershipManager.currentInstance().getOtherMembers().size();
		
		
		
		
		if (currentGroupSize == 0) {
			System.err.println("	>>>- LE: I'M THE ONLY ONE, THUS I'M THE LEADER: " + RFIFOMulticast.getMyHostName());
			//announceTheNewLeader(ReplicaMember.currentInstance().getProcessId(),  RFIFOMulticast.getMyHostName());
			return;
		}
		
		
		
		giveMeYourIdMsg.put("MEMBERS_COUNT", "" + currentGroupSize);
		RFIFOMulticast rfm = new RFIFOMulticast(GroupMembershipManager.currentInstance().getOtherMembers());
		ResultHandler sucessHandler = new ResultHandler() {
			
			@Override
			public void handleResult(Message msg) {
				String replicaHostName = msg.get(RFIFOMulticast.RFM_MSG_SENDER);
				Integer replicaProcessId = Integer.parseInt(msg.get(REPLICA_PROCESS_ID));
				int membersCount = msg.getAsInt("MEMBERS_COUNT");
				replicaIds.put(replicaHostName, replicaProcessId);
				if (replicaIds.size() == membersCount) {
					processIDSHash();
				}
			}

		};
		ResultHandler failureHandler = new ResultHandler() {
			
			@Override
			public void handleResult(Message msg) {
				System.err.println("	>>>ERROR - LE: Something went wrong in failure LEADER GIVE ME YOUR ID: " + msg.get(RFIFOMulticast.RFM_MSG_DEAD_HOST));
				
			}
		};
		rfm.send(ApplicationConfig.getAsInt(LEADERELECTION_SERVER_PORT), giveMeYourIdMsg, sucessHandler, failureHandler);
	}

	private void processIDSHash() {
		int newLeaderProcessID = ReplicaMember.currentInstance().getProcessId();
		String newLeaderHostName = RFIFOMulticast.getMyHostName();
		for (Iterator<String> iterator = replicaIds.keySet().iterator(); iterator.hasNext();) {
			String processName = (String) iterator.next();
			int processId =  replicaIds.get(processName);
			if (processId > newLeaderProcessID) {
				newLeaderProcessID = processId;
				newLeaderHostName = processName;
			}
		}
		System.err.println("	>>>LE: THE NEW LEADER IS" + newLeaderHostName);
		announceTheNewLeader(newLeaderProcessID, newLeaderHostName);
	}
	
	private void announceTheNewLeader(int newLeaderProcessID, String newLeaderHostName) {
		this.currentLeaderProcessId = newLeaderProcessID;
		this.currentLeaderHostname = newLeaderHostName;
		if (ReplicaMember.currentInstance().getProcessId() == newLeaderProcessID) {
			ReplicaMember.currentInstance().setState(ReplicaMember.REPLICA_STATE_LEADER);
		} else {
			ReplicaMember.currentInstance().setState(ReplicaMember.REPLICA_STATE_MEMBER);
		}
		Message msg = new Message();
		msg.put(KEY_MSG_TYPE, LE_TYPE_COORD_MSG);
		msg.put("NEW_LEADER_PROCESS_ID", "" + newLeaderProcessID);
		msg.put("NEW_LEADER_HOST_NAME", "" + newLeaderHostName);
		List<String> members = GroupMembershipManager.currentInstance().getOtherMembers();
		if (members.size() != 0) {
			RFIFOMulticast rfm = new RFIFOMulticast(members);
			System.err.println("	>>>LE: ANNOUNCING A NEW LEADER");
			rfm.send(ApplicationConfig.getAsInt(LEADERELECTION_SERVER_PORT), msg, null, null);
		}
	}

	/*
	 * The lisneter handler to support the request
	 */
	ListernerHandler listenerHandler = new ListernerHandler() {

		@Override
		public Message handleResult(Message msg) {
			String msg_type = msg.get(KEY_MSG_TYPE);
			if (msg_type.equals(LE_TYPE_GET_ID)){
				System.out.println("	>>>INFO: Leader election, LE_TYPE_GET_ID received.");
				Message response = new Message();
				response.put(REPLICA_PROCESS_ID, "" + ReplicaMember.currentInstance().getProcessId());
				return response;
			} else if (msg_type.equals(LE_TYPE_COORD_MSG)) { // other member  declared as it self as leader!
				System.out.println("	>>>INFO: Leader election, LE_TYPE_COORD_MSG received.");
				currentLeaderProcessId = Integer.parseInt(msg.get("NEW_LEADER_PROCESS_ID"));
				currentLeaderHostname = msg.get("NEW_LEADER_HOST_NAME");
				if (ReplicaMember.currentInstance().getProcessId() == currentLeaderProcessId) {
					ReplicaMember.currentInstance().setState(ReplicaMember.REPLICA_STATE_LEADER);
				} else {
					ReplicaMember.currentInstance().setState(ReplicaMember.REPLICA_STATE_MEMBER);
				}
			}
			return null;
		}
	};

	/**
	 * Get the id for the current Leader. Used by Failure detection
	 * 
	 * @return
	 */
	public int getIdLeader() {
		return currentLeaderProcessId;
	}

	public String getNameLeader() {
		return currentLeaderHostname;
	}

}
